/**
 * Copyright 2011 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.api.adwords.starterkit.wrappers;

import com.google.api.adwords.starterkit.db.keys.CampaignKey;
import com.google.api.adwords.starterkit.db.keys.NegativeKeywordKey;
import com.google.api.adwords.starterkit.db.structures.CampaignStructure;
import com.google.api.adwords.starterkit.db.tables.CampaignTable;
import com.google.api.adwords.starterkit.settings.AdGroupSettings;
import com.google.api.adwords.starterkit.settings.CampaignSettings;
import com.google.api.adwords.v201101.cm.BudgetBudgetDeliveryMethod;
import com.google.api.adwords.v201101.cm.BudgetBudgetPeriod;
import com.google.api.adwords.v201101.cm.CampaignStatus;
import com.google.api.adwords.v201101.cm.KeywordMatchType;

import org.apache.log4j.Logger;

import java.sql.Connection;
import java.util.HashMap;

/**
 * The Class CampaignWrapper manages all AdGroups synchronization process with
 * the StarterKit DB.
 */
public class CampaignWrapper {
  private CampaignSettings campaignSettings;
  private Connection conn = null;

  private CampaignStructure campaignStructure;
  private LanguageTargetWrapper languageTarget;
  private PlatformTargetWrapper platformTarget;
  private NetworkTargetWrapper networkTarget;
  private GeoTargetWrapper geoTarget;
  private CampaignTable campaignTable;

  private static final Logger log = Logger.getLogger("StarterKit");

  private HashMap<String, AdGroupWrapper> adGroupWrappers =
      new HashMap<String, AdGroupWrapper>();
  private HashMap<NegativeKeywordKey, NegativeKeywordWrapper> 
      negativeKeywordWrappers 
      = new HashMap<NegativeKeywordKey, NegativeKeywordWrapper>();

  /**
   * Instantiates a new campaign wrapper.
   * 
   * @param name the name
   * @param campaignSettings the starter kit settings
   * @param conn the conn
   */
  public CampaignWrapper(final String name,
      final CampaignSettings campaignSettings, Connection conn) {
    this.conn = conn;
    this.campaignSettings = campaignSettings;

    campaignStructure = new CampaignStructure();

    campaignStructure.setEndDate(campaignSettings.getEndDay());
    campaignStructure.setStatus(campaignSettings.getStatus());
    campaignStructure.setBudgetAmount(campaignSettings.getBudgetAmount());
    campaignStructure.setBudgetPeriod(campaignSettings.getBudgetPeriod());
    campaignStructure.setName(name);
    campaignStructure.setBiddingStrategy(campaignSettings.getBiddingStrategy());
    campaignStructure.setBidCeiling(campaignSettings.getBidCeiling());
    campaignStructure.setPricingModel(campaignSettings.getPricingModel());
    campaignStructure.setDeliveryMethod(campaignSettings.getDeliveryMethod());

    campaignStructure.setBudgetModifiable(true);
    campaignStructure.setEndDateModifiable(true);
    campaignStructure.setStatusModifiable(true);

    campaignTable = new CampaignTable(conn);

    CampaignKey campaignKey = new CampaignKey(name);
    if (campaignTable.exists(campaignKey)) {
      synchronize(campaignKey);
      campaignTable.setIsTouched(campaignKey, true);
    } else {
      campaignStructure = campaignTable.save(campaignStructure, true, true);
    }

    setLanguageTarget(new LanguageTargetWrapper(this, campaignSettings, conn));
    setNetworkTarget(new NetworkTargetWrapper(this, campaignSettings, conn));
    setGeoTarget(new GeoTargetWrapper(this, campaignSettings, conn));
    setPlatformTarget(new PlatformTargetWrapper(this, campaignSettings, conn));
  }

  /**
   * Gets the ad group wrapper based on the AdGroup Name.
   */
  public AdGroupWrapper getAdGroupWrapper(String adGroupName,
      AdGroupSettings adGroupSettings) {
    if (adGroupWrappers.containsKey(adGroupName)) {
      return adGroupWrappers.get(adGroupName);
    } else {
      AdGroupWrapper adGroupWrapper =
          new AdGroupWrapper(this, adGroupName, adGroupSettings, conn);
      adGroupWrappers.put(adGroupName, adGroupWrapper);
      return adGroupWrapper;
    }
  }

  /**
   * Gets the negative keyword wrapper.
   * 
   * @param text the text
   * @param matchType the match type
   * @return the negative keyword wrapper
   */
  public NegativeKeywordWrapper getNegativeKeywordWrapper(String text,
      KeywordMatchType matchType) {
    NegativeKeywordKey negativeKeywordKey =
        new NegativeKeywordKey(campaignStructure.getLocalId(), matchType, text);
    if (negativeKeywordWrappers.containsKey(negativeKeywordKey)) {
      return negativeKeywordWrappers.get(negativeKeywordKey);
    } else {
      NegativeKeywordWrapper negativeKeywordWrapper =
          new NegativeKeywordWrapper(this, text, matchType, campaignSettings,
              conn);
      negativeKeywordWrappers.put(negativeKeywordKey, negativeKeywordWrapper);
      return negativeKeywordWrapper;
    }
  }

  public float getCtr() {
    return campaignStructure.getCtr();
  }

  public long getClicks() {
    return campaignStructure.getClicks();
  }

  public float getCost() {
    return campaignStructure.getCost();
  }

  public long getImpressions() {
    return campaignStructure.getImpressions();
  }

  public long getConversions() {
    return campaignStructure.getConversions();
  }

  public long getTransactions() {
    return campaignStructure.getTransactions();
  }

  public String getName() {
    return campaignStructure.getName();
  }

  public String getStartDate() {
    return campaignStructure.getStartDate();
  }

  public String getEndDate() {
    return campaignStructure.getEndDate();
  }

  public CampaignStatus getStatus() {
    return campaignStructure.getStatus();
  }

  public long getBudgetAmount() {
    return campaignStructure.getBudgetAmount();
  }

  public long getCampaignId() {
    return campaignStructure.getCampaignId();
  }

  public int getLocalId() {
    return campaignStructure.getLocalId();
  }

  public BudgetBudgetPeriod getBudgetPeriod() {
    return campaignStructure.getBudgetPeriod();
  }

  /**
   * Sets the modified language flag. When true, informs the framework to update
   * the languages targets linked to this campaign. This flags behavior is not 
   * the same of canModifyXXXFlags series. 
   * 
   * @param modifiedLanguage the new modified language flag
   */
  public void setModifiedLanguage(boolean modifiedLanguage) {
    campaignStructure.setLanguageModified(modifiedLanguage);
    campaignTable.setLangFlag(new CampaignKey(campaignStructure.getName()),
        modifiedLanguage);
  }

  /**
   * Sets the modified platform flag. When true, informs the framework to update
   * the platforms linked to this campaign. This flags behavior is not 
   * the same of canModifyXXXFlags series. 
   * 
   * @param modifiedPlatform the new modified platform flag
   */
  public void setModifiedPlatform(boolean modifiedPlatform) {
    campaignStructure.setPlatformModified(modifiedPlatform);
    campaignTable.setLangFlag(new CampaignKey(campaignStructure.getName()),
        modifiedPlatform);
  }

  /**
   * Sets the modified geo flag. When true, informs the framework to update the
   * geotargets linked to this campaign. This flags behavior is not 
   * the same of canModifyXXXFlags series.
   * 
   * @param modifiedGeo the new modified geo flag
   */
  public void setModifiedGeo(boolean modifiedGeo) {
    campaignStructure.setGeoModified(modifiedGeo);
    campaignTable.setGeoFlag(new CampaignKey(campaignStructure.getName()),
        modifiedGeo);
  }

  /**
   * Sets the modified network flag. When true, informs the framework to update
   * the networkTarget linked to this campaign. This flags behavior is not 
   * the same of canModifyXXXFlags series.
   * 
   * @param modifiedNetwork the new modified network flag
   */
  public void setModifiedNetwork(boolean modifiedNetwork) {
    campaignStructure.setNetworkModified(modifiedNetwork);
    campaignTable.setNetFlag(new CampaignKey(campaignStructure.getName()),
        modifiedNetwork);
  }

  public void setBiddingStrategy(String biddingStrategy) {
    biddingStrategy = biddingStrategy.toLowerCase();
    if (!campaignStructure.getBiddingStrategy().equals(biddingStrategy)) {
      campaignStructure.setBiddingStrategy(biddingStrategy.toLowerCase());
      campaignTable.save(campaignStructure, true, true);
    }
  }

  public void setBudget(long budgetAmount) {
    campaignStructure.setBudgetAmount(budgetAmount);
    campaignTable.save(campaignStructure, true, true);
  }

  public void setBudgetPeriod(BudgetBudgetPeriod budgetPeriod) {
    campaignStructure.setBudgetPeriod(budgetPeriod);
    campaignTable.save(campaignStructure, true, true);
  }

  public void setEndDate(String endDate) {
    campaignStructure.setEndDate(endDate);
    campaignTable.save(campaignStructure, true, true);
  }

  public void setDeliveryMethod(BudgetBudgetDeliveryMethod deliveryMethod) {
    campaignStructure.setDeliveryMethod(deliveryMethod);
    campaignTable.save(campaignStructure, true, true);
  }

  public void setStatus(CampaignStatus status) {
    campaignStructure.setStatus(status);
    campaignTable.save(campaignStructure, true, true);
  }

  public void setLocalId(int localId) {
    campaignStructure.setLocalId(localId);
  }

  public void setCampaignId(long campaignId) {
    campaignStructure.setCampaignId(campaignId);
  }

  public boolean getCanModifyBudget() {
    return campaignStructure.isBudgetModifiable();
  }

  public boolean getCanModifyEndDate() {
    return campaignStructure.isEndDateModifiable();
  }

  public boolean getCanModifyStatus() {
    return campaignStructure.isStatusModifiable();
  }

  /**
   * Sets the can modify budget flag. If set to false, StarterKit will not
   * modify the Campaign budget even when direct requested by application.
   * See the canModifyXXXFlags at the manual for a deep dive explanation. 
   */
  public void setCanModifyBudget(boolean canModifyBudget) {
    campaignStructure.setBudgetModifiable(canModifyBudget);
    campaignTable.save(campaignStructure, false, false);
  }

  /**
   * Sets the can modify end date flag. If set to false, StarterKit will not
   * modify the Campaign End Date even when direct requested by application.
   * See the canModifyXXXFlags at the manual for a deep dive explanation. 
   */
  public void setCanModifyEndDate(boolean canModifyEndDate) {
    campaignStructure.setEndDateModifiable(canModifyEndDate);
    campaignTable.save(campaignStructure, false, false);
  }

  /**
   * Sets the can modify status flag. If set to false, StarterKit will not
   * modify the Campaign Status even when direct requested by application.
   * See the canModifyXXXFlags at the manual for a deep dive explanation. 
   */
  public void setCanModifyStatus(boolean canModifyStatus) {
    campaignStructure.setStatusModifiable(canModifyStatus);
    campaignTable.save(campaignStructure, false, false);
  }

  public LanguageTargetWrapper getLanguageTarget() {
    if (languageTarget == null) {
      languageTarget = new LanguageTargetWrapper(this, campaignSettings, conn);
    }
    return languageTarget;
  }

  public PlatformTargetWrapper getPlatformTarget() {
    if (platformTarget == null) {
      platformTarget = new PlatformTargetWrapper(this, campaignSettings, conn);
    }
    return platformTarget;
  }

  /**
   * Synchronize a Campaign with the campaign on DB based on campaignKey.
   */
  public void synchronize(CampaignKey campaignKey) {

    CampaignStructure campaignDB = campaignTable.get(campaignKey);

    // sync DB data
    campaignStructure.setLocalId(campaignDB.getLocalId());
    campaignStructure.setCampaignId(campaignDB.getCampaignId());
    campaignStructure.setBanned(campaignDB.isBanned());

    campaignStructure.setBudgetModifiable(campaignDB.isBudgetModifiable());
    campaignStructure.setEndDateModifiable(campaignDB.isEndDateModifiable());
    campaignStructure.setStatusModifiable(campaignDB.isStatusModifiable());

    boolean modified = false;

    // Can I update budget? based on settings
    if (campaignSettings.getCanModifyBudget()) {
      if (campaignStructure.getBudgetAmount() != campaignDB.getBudgetAmount()) {
        // Can I update budget? Based on web interface changes
        if (!campaignDB.isBudgetModifiable()) {
          campaignStructure.setBudgetAmount(campaignDB.getBudgetAmount());
        } else {
          // make sure we will make values on campaignStructure persistent
          campaignStructure.setBidModified(true);
          modified = true;
        }
      }

      if (!campaignStructure.getBiddingStrategy()
          .equals(campaignDB.getBiddingStrategy())) {
        if (!campaignDB.isBudgetModifiable()) {
          campaignStructure.setBiddingStrategy(campaignDB.getBiddingStrategy());
        } else {
          // make sure we will make values on campaignStructure persistent
          campaignStructure.setBiddingModified(true);
          campaignStructure
              .setOldBiddingStrategy(campaignDB.getBiddingStrategy());
          modified = true;
          String newBid = campaignStructure.getBiddingStrategy();
          String oldBid = campaignStructure.getOldBiddingStrategy();
          
          // check for valid bidding strategy migrations
          boolean bidsCpc2Cpm
              = oldBid.equals("manualcpc") && newBid.equals("manualcpm");
          boolean bidsCpm2Cpc 
              = oldBid.equals("manualcpm") && newBid.equals("manualcpc");          
          boolean bidsCpc2ConOpt = oldBid.equals("manualcpc") 
              && newBid.equals("conversionoptimizer");
          boolean bidsCpc2BudOpt
              = oldBid.equals("manualcpc") && newBid.equals("budgetoptimizer");
          boolean bidsConopt2Cpc = oldBid.equals("conversionoptimizer") 
              && newBid.equals("manualcpc");
          boolean bidsBudOpt2Cpc = oldBid.equals("budgetoptimizer") 
              && newBid.equals("manualcpc");
          
          if (!(bidsCpc2Cpm
              || bidsCpm2Cpc
              || bidsCpc2ConOpt
              || bidsCpc2BudOpt
              || bidsConopt2Cpc 
              || bidsBudOpt2Cpc)) {
            log.error("Sorry! Starter Kit can only deal with the followings "
                + "Bidding Strategy Migrations:");
            log.error("ManualCPC <-> Manual CPM");
            log.error("ManualCPC <-> ConversionOptimizer");
            log.error("ManualCPC <-> BudgetOptimizer");            
          }
        }
      }

      if (campaignStructure.getBidCeiling() != campaignDB.getBidCeiling()) {
        if (!campaignDB.isBudgetModifiable()) {
          campaignStructure.setBidCeiling(campaignDB.getBidCeiling());
        } else {
          // make sure we will make values on campaignStructure persistent
          campaignStructure.setBidModified(true);
          modified = true;
        }
      }

      if (!campaignStructure.getPricingModel()
          .equals(campaignDB.getPricingModel())) {
        if (!campaignDB.isBudgetModifiable()) {
          campaignStructure.setPricingModel(campaignDB.getPricingModel());
        } else {
          // make sure we will make values on campaignStructure persistent
          modified = true;
          campaignStructure.setBidModified(true);
        }
      }

      if (!campaignStructure.getBudgetPeriod()
          .equals(campaignDB.getBudgetPeriod())) {
        if (!campaignDB.isBudgetModifiable()) {
          campaignStructure.setBudgetPeriod(campaignDB.getBudgetPeriod());
        } else {
          // make sure we will make values on campaignStructure persistent
          modified = true;
        }
      }

      if (!campaignStructure.getDeliveryMethod()
          .equals(campaignDB.getDeliveryMethod())) {
        if (!campaignDB.isBudgetModifiable()) {
          campaignStructure.setDeliveryMethod(campaignDB.getDeliveryMethod());
        } else {
          // make sure we will make values on campaignStructure persistent
          modified = true;
        }
      }
    } else {
      campaignStructure.setBudgetAmount(campaignDB.getBudgetAmount());
      campaignStructure.setBidCeiling(campaignDB.getBidCeiling());
      campaignStructure.setBiddingStrategy(campaignDB.getBiddingStrategy());
      campaignStructure.setBudgetPeriod(campaignDB.getBudgetPeriod());
      campaignStructure.setDeliveryMethod(campaignDB.getDeliveryMethod());
      campaignStructure.setPricingModel(campaignDB.getPricingModel());
    }

    if (campaignSettings.getCanModifyStatus()) {
      if (!campaignStructure.getStatus().equals(campaignDB.getStatus())) {
        if (!campaignDB.isStatusModifiable()) {
          campaignStructure.setStatus(campaignDB.getStatus());
        } else {
          modified = true;
        }
      }
    } else {
      campaignStructure.setStatus(campaignDB.getStatus());
    }

    if (!campaignStructure.getEndDate().equals(campaignDB.getEndDate())) {
      if (!campaignDB.isEndDateModifiable()) {
        campaignStructure.setEndDate(campaignDB.getEndDate());
      } else {
        modified = true;
      }
    }

    if (modified) {
      campaignTable.save(campaignStructure, true, true);
    }
  }

  public CampaignSettings getCampaignSettings() {
    return campaignSettings;
  }

  public void setNetworkTarget(NetworkTargetWrapper networkTarget) {
    this.networkTarget = networkTarget;
  }

  public NetworkTargetWrapper getNetworkTarget() {
    return networkTarget;
  }

  public void setGeoTarget(GeoTargetWrapper geoTarget) {
    this.geoTarget = geoTarget;
  }

  public GeoTargetWrapper getGeoTarget() {
    return geoTarget;
  }

  public void setLanguageTarget(LanguageTargetWrapper languageTarget) {
    this.languageTarget = languageTarget;
  }

  public void setPlatformTarget(PlatformTargetWrapper platformTarget) {
    this.platformTarget = platformTarget;
  }
}
